feat(android): [Unhandled Sessions 3] Add internal non-terminating envelope capture - #5921
feat(android): [Unhandled Sessions 3] Add internal non-terminating envelope capture#5921buenaflor wants to merge 15 commits into
Conversation
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
📲 Install BuildsAndroid
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 075631e | 355.90 ms | 458.42 ms | 102.52 ms |
| c2189fd | 300.61 ms | 362.84 ms | 62.22 ms |
| f4f202f | 315.94 ms | 369.24 ms | 53.30 ms |
| 8014a1f | 304.46 ms | 376.38 ms | 71.92 ms |
| 211ab30 | 314.79 ms | 389.47 ms | 74.68 ms |
| bc96797 | 321.86 ms | 377.38 ms | 55.52 ms |
| ed254d0 | 331.02 ms | 359.08 ms | 28.06 ms |
| b696cfd | 320.00 ms | 358.66 ms | 38.66 ms |
| 4f9af8d | 329.51 ms | 342.60 ms | 13.09 ms |
| 0650636 | 387.86 ms | 457.88 ms | 70.02 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 075631e | 0 B | 0 B | 0 B |
| c2189fd | 0 B | 0 B | 0 B |
| f4f202f | 0 B | 0 B | 0 B |
| 8014a1f | 0 B | 0 B | 0 B |
| 211ab30 | 0 B | 0 B | 0 B |
| bc96797 | 0 B | 0 B | 0 B |
| ed254d0 | 0 B | 0 B | 0 B |
| b696cfd | 0 B | 0 B | 0 B |
| 4f9af8d | 0 B | 0 B | 0 B |
| 0650636 | 0 B | 0 B | 0 B |
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
6 similar comments
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
56ab999 to
1906ec6
Compare
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
2 similar comments
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
06f5823 to
ae23921
Compare
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
ae23921 to
b2ff9d9
Compare
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
b2ff9d9 to
945602b
Compare
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…thods Both methods scanned the envelope's events to derive the same pair of booleans, but wrote it differently - one via isCrashed(), the other via getUnhandledException() != null, which is the same predicate. Extract a single scanEvents returning NONE/ERRORED/UNHANDLED so the two agree by construction and an unhandled-but-not-errored state is unrepresentable. Co-authored-by: Cursor <cursoragent@cursor.com>
The annotation had no mechanical effect: apiValidation configures only ignoredPackages/ignoredProjects and no nonPublicMarkers, so the type is tracked in sentry.api either way. Regenerating the dump after removing it produces no diff. The interface still has to be public, since the lambda in InternalSentrySdk.captureEnvelopeNonTerminating targets it from io.sentry.android.core. Co-authored-by: Cursor <cursoragent@cursor.com>
Both the method and the enum were plural nouns that read as if they returned the envelope's events, when they return a single summary value. That made "events != EnvelopeEvents.NONE" look like an emptiness check rather than "nothing worth recording happened". EnvelopeEventState also lines up with the Session.State vocabulary already used here. Co-authored-by: Cursor <cursoragent@cursor.com>
Same reasoning as the cache change: this catch predates the feature, and narrowing it changed how an Error during capture behaves for every existing caller while leaving the file's three other catch (Throwable) blocks untouched. captureEnvelopeNonTerminating and readEnvelope are new code and keep catch (Exception). Co-authored-by: Cursor <cursoragent@cursor.com>
5a16f9b to
b01be8a
Compare
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
There was a problem hiding this comment.
Pull request overview
Adds a new internal capture path for hybrid Android runtimes (e.g. Flutter) to report handled=false events that don’t terminate the process, ensuring the active session remains Ok (same SID) while recording a non-terminating unhandled error that will later finalize as unhandled via normal lifecycle/persistence flows.
Changes:
- Add
InternalSentrySdk.captureEnvelopeNonTerminating(byte[])that flags/persists the current session without attaching a session item or starting a new session. - Expose
Scope.IWithSessionso Android-core code can mutate session state under the scope lock; update API dumps accordingly. - Add Android-core tests for the new non-terminating behavior and persist-to-disk semantics; add Truth as a test dependency; add changelog entry.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| sentry/src/main/java/io/sentry/Scope.java | Makes IWithSession public to enable session mutation under the scope lock from other modules. |
| sentry/api/sentry.api | API dump updated to include the now-public Scope.IWithSession. |
| sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java | Adds captureEnvelopeNonTerminating, plus shared envelope read/event scanning helpers and session persistence hook. |
| sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt | Adds unit tests validating non-terminating unhandled behavior (session stays Ok, flag persisted, later finalization). |
| sentry-android-core/build.gradle.kts | Adds Google Truth as a test dependency for new assertions. |
| sentry-android-core/api/sentry-android-core.api | API dump updated for new InternalSentrySdk.captureEnvelopeNonTerminating. |
| CHANGELOG.md | Adds an Unreleased “Internal” entry documenting the new internal hybrid capture API. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ions-internal-api
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
…ions-internal-api
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
…ions-internal-api
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
…ions-internal-api
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
…ions-internal-api
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
Matches IWithTransaction and IWithPropagationContext. The interface has to be public for InternalSentrySdk to use it, but it is not supported API. Co-authored-by: Cursor <cursoragent@cursor.com>
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
PR Stack (Unhandled Sessions)
📜 Description
Adds
InternalSentrySdk.captureEnvelopeNonTerminating(byte[])for hybrid runtimes where an unhandled exception does not terminate the process.Unlike
captureEnvelope(byte[], boolean), it does not treathandled=falseas a crash. Instead it:Okwith the same session id on the scope,The session is finalized later by normal lifecycle (
endSession, background, or previous-session recovery) asunhandled, unless a native crash escalates it tocrashed.captureEnvelope(byte[], boolean)is unchanged.Also in this PR:
Scope.IWithSessionbecomes public soInternalSentrySdkcan mutate the session under the scope lock; it is the only consumer. It carries@ApiStatus.InternallikeIWithTransactionandIWithPropagationContext, so it is not supported API despite being public.eventStateOfreturningEnvelopeEventState, replacing two loops that computed the same booleans by different routes, plus a sharedreadEnvelope.Exceptionrather thanThrowable, soOutOfMemoryErrorand friends propagate instead of being swallowed. The existingcaptureEnvelopekeeps itscatch (Throwable).💡 Motivation and Context
Flutter forwards
handled=falseevents through the terminating hybrid capture path. That marks the sessioncrashedand may start a replacement session even though the Flutter process keeps running, incorrectly lowering crash-free session rates.💚 How did you test it?
New
InternalSentrySdkTestcoverage: the session stayingOkwith the same id and the flag persisted to disk;endSessionafterwards finalizing asunhandled; and a later hard crash finalizing the old session ascrashedand starting a freshOksession. ExistingcaptureEnvelopetests confirm that path is unchanged.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Update the Flutter Android bridge to use
captureEnvelopeNonTerminatingfor non-terminating unhandled events.